package com.zg.generator.service.impl;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zg.common.core.dao.generator.GenColumn;
import com.zg.common.core.dao.generator.GenColumnDao;
import com.zg.common.core.dao.generator.GenTmpl;
import com.zg.common.core.vo.ColumnVo;
import com.zg.generator.config.EngineConfig;
import com.zg.common.core.dao.ColumnConstant;
import com.zg.common.core.enm.ColumnTypeEnum;
import com.zg.common.web.service.DdlService;
import com.zg.generator.service.EngineService;
import com.zg.generator.util.FreeMarkerUtil;
import com.zg.generator.util.NameUtil;
import com.zg.generator.vo.*;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Zxb
 */
@Service
@AllArgsConstructor
@Slf4j
public class EngineServiceImpl implements EngineService {
    private GenColumnDao columnDao;

    private DdlService ddlService;

    @Override
    public List<GenColumn> findGenColumnByTable(String tableName) {
        return columnDao.list(
                new LambdaQueryWrapper<GenColumn>()
                        .eq(GenColumn::getTableName, tableName)
                        .orderByAsc(GenColumn::getId));
    }

    @Override
    public GenColumn findGenColumnByTable(String tableName, String columnName) {
        return columnDao.getOne(
                new LambdaQueryWrapper<GenColumn>()
                        .eq(GenColumn::getTableName, tableName)
                        .eq(GenColumn::getColumnName, columnName)
                        .orderByAsc(GenColumn::getId));
    }

    @Override
    public void generateCode(String schema, String tableName, List<GenTmpl> tmplList) {
        ControllerVo c = buildController(schema, tableName);
        VueVo vue = buildVue(tableName);
        EntityVo entity = buildEntity(schema, tableName);
        for (GenTmpl tmpl : tmplList) {
            if (tmpl.getType().equals("1")) {
                FreeMarkerUtil.generate(c, tmpl, c.getName() + tmpl.getSuffix());
            } else if (tmpl.getType().equals("2")) {
                FreeMarkerUtil.generate(vue, tmpl, tmpl.getSuffix());
            } else {
                FreeMarkerUtil.generate(entity, tmpl, entity.getName() + tmpl.getSuffix());
            }
        }
    }

    private EntityVo buildEntity(String schema, String tableName) {
        EntityVo entity = new EntityVo();
        entity.setPackageName(EngineConfig.packageName.get());
        String className = NameUtil.genClassName(tableName);
        entity.setName(className);
        entity.setHasDate(false);
        entity.setHasTime(false);
        entity.setHasDatetime(false);
        entity.setHasNeed(false);
        entity.setHasRegex(false);

        List<ColumnVo> colList = ddlService.findColumnByTable(tableName, schema);
        Set<String> proTypeSet = new LinkedHashSet<>();
        List<PropertyVo> proList = new ArrayList<>();
        List<PropertyVo> addList = new ArrayList<>();
        List<PropertyVo> updateList = new ArrayList<>();
        List<PropertyVo> batchList = new ArrayList<>();
        List<PropertyVo> likeList = new ArrayList<>();
        for (ColumnVo col : colList) {

            String columnName = col.getName();
            if (isPubColumn(columnName)) {
                continue;
            }
            Class<?> type = ColumnTypeEnum.oracle2Java(col.getType());
            PropertyVo pro = new PropertyVo();
            pro.setName(NameUtil.genProName(columnName));
            pro.setColumnVo(col);
            pro.setType(type);
            pro.setShortTypeName(type.getSimpleName());
            GenColumn gc = findGenColumnByTable(tableName, columnName);
            pro.setGenColumn(gc);
            if (StrUtil.isNotBlank(gc.getRegex())) {
                entity.setHasRegex(true);
            }
            if (gc.getIsNeed() != null && gc.getIsNeed()) {
                entity.setHasNeed(true);
            }
            if (ColumnTypeEnum.DATE.getName().equals(gc.getDataType())) {
                entity.setHasDate(true);
            } else if (ColumnTypeEnum.DATETIME.getName().equals(gc.getDataType())) {
                entity.setHasDatetime(true);
            } else if (ColumnTypeEnum.TIME.getName().equals(gc.getDataType())) {
                entity.setHasTime(true);
            }
            String referTableName = gc.getReferTableName();
            if (StrUtil.isBlank(referTableName)) {
                proTypeSet.add(type.getName());
            }
            proList.add(pro);
            if (gc.getAddShow() != null && gc.getAddShow()) {
                addList.add(pro);
            }
            if (gc.getUpdateShow() != null && gc.getUpdateShow()) {
                updateList.add(pro);
            }
            if (gc.getBatchShow() != null && gc.getBatchShow()) {
                batchList.add(pro);
            }
            if (gc.getLikeShow() != null && gc.getLikeShow()) {
                likeList.add(pro);
            }
        }
        entity.setProList(proList);
        entity.setAddList(addList);
        entity.setUpdateList(updateList);
        entity.setBatchList(batchList);
        entity.setLikeList(likeList);
        entity.setProTypeSet(proTypeSet);
        return entity;
    }

    private ControllerVo buildController(String schema, String tableName) {
        String name = NameUtil.genClassName(tableName);
        String mapping = NameUtil.genMapping(tableName);
        String entityImport = NameUtil.genEntityImport(tableName);
        List<PropertyVo> likeList = new ArrayList<>();
        List<PropertyVo> batchList = new ArrayList<>();
        List<ColumnVo> colList = ddlService.findColumnByTable(tableName, schema);
        Boolean hasSortColumn = false;
        String sortColumnName = "";
        for (ColumnVo col : colList) {
            String columnName = col.getName();
            if (isPubColumn(columnName)) {
                continue;
            }
            Class<?> type = ColumnTypeEnum.oracle2Java(col.getType());
            PropertyVo pro = new PropertyVo();
            pro.setName(NameUtil.genProName(columnName));
            pro.setColumnVo(col);
            pro.setType(type);
            pro.setShortTypeName(type.getSimpleName());

            GenColumn gc = columnDao.getOne(
                    new LambdaQueryWrapper<GenColumn>()
                            .eq(GenColumn::getTableName, tableName)
                            .eq(GenColumn::getColumnName, col.getName()));
            pro.setGenColumn(gc);

            if (gc != null) {
                if (gc.getBatchShow() != null && gc.getBatchShow()) {
                    batchList.add(pro);
                }
                if (gc.getLikeShow() != null && gc.getLikeShow()) {
                    likeList.add(pro);
                }
                if (gc.getIsSort() != null && gc.getIsSort()) {
                    hasSortColumn = true;
                    sortColumnName = gc.getColumnName();
                }
            }
        }

        ControllerVo c = new ControllerVo();
        c.setPackageName(EngineConfig.packageName.get());
        c.setName(name);
        c.setMapping(mapping);
        c.setEntityImport(entityImport);
        c.setBatchList(batchList);
        c.setLikeList(likeList);
        c.setHasSortColumn(hasSortColumn);
        c.setSortColumnName(sortColumnName);
        return c;
    }

    private VueVo buildVue(String tableName) {
        List<GenColumn> list = findGenColumnByTable(tableName);
        List<PropertyVo> allList = new ArrayList<>();
        List<PropertyVo> addList = new ArrayList<>();
        List<PropertyVo> updateList = new ArrayList<>();
        List<PropertyVo> batchList = new ArrayList<>();
        Boolean hasSortColumn = false;
        String sortColumnName = "";
        for (GenColumn gc : list) {
            String columnName = gc.getColumnName();
            if (isPubColumn(columnName)) {
                continue;
            }
            PropertyVo pro = new PropertyVo();
            pro.setCompType(gc.getCompType());
            pro.setColumnVo(new ColumnVo(gc.getColumnName(), gc.getColumnType(), gc.getFormLabel(), gc.getColumnLength()));
            pro.setName(NameUtil.genProName(gc.getColumnName()));
            pro.setGenColumn(gc);
            if (gc.getAddShow() != null && gc.getAddShow()) {
                addList.add(pro);
            }
            if (gc.getUpdateShow() != null && gc.getUpdateShow()) {
                updateList.add(pro);
            }
            if (gc.getBatchShow() != null && gc.getBatchShow()) {
                batchList.add(pro);
            }
            if (gc.getIsSort() != null && gc.getIsSort()) {
                hasSortColumn = true;
                sortColumnName = gc.getColumnName();
            }
            allList.add(pro);
        }

        VueVo vue = new VueVo();
        vue.setBaseMapping(NameUtil.genMapping(tableName));
        vue.setDescription(tableName);
        vue.setAllList(allList);
        vue.setAddList(addList);
        vue.setUpdateList(updateList);
        vue.setBatchList(batchList);
        vue.setHasSortColumn(hasSortColumn);
        vue.setSortColumnName(sortColumnName);
        return vue;
    }

    @Override
    public Boolean isPubColumn(String name) {
        return ColumnConstant.PUB_COLUMNS.contains(name);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<GenColumn> initTable(String schema, String tableName, List<ColumnVo> colList) {
        List<GenColumn> list = new ArrayList<>();
        int sort = 1;
        for (ColumnVo col : colList) {
            if (ColumnConstant.PUB_COLUMNS.contains(col.getName())) {
                continue;
            }
            if (columnDao.exists(
                    new LambdaQueryWrapper<GenColumn>()
                            .eq(GenColumn::getTableName, tableName)
                            .eq(GenColumn::getColumnName, col.getName())
            )) {
                continue;
            }
            GenColumn column = new GenColumn();
            column.setTableName(tableName);
            column.setColumnName(col.getName());
            String type = col.getType();
            column.setColumnType(type);
            column.setColumnLength(col.getLength());
            column.setCompType("");
            column.setFormLabel(col.getComment());
            column.setReferTableName("");
            column.setSearchShow(true);
            column.setLikeShow(false);
            column.setAddShow(true);
            column.setUpdateShow(true);
            column.setBatchShow(true);
            column.setOpts("[]");
            column.setHeaderName(col.getComment());
            column.setHeaderWidth(120);
            column.setHeaderShow(true);
            column.setHeaderSort(sort);
            sort++;
            list.add(column);
        }
        columnDao.saveBatch(list);
        List<GenColumn> genColumnList = columnDao.list(
                new LambdaQueryWrapper<GenColumn>()
                        .eq(GenColumn::getTableName, tableName)
        );
        for (GenColumn genColumn : genColumnList) {
            if (colList.stream().anyMatch(t -> t.getName().equals(genColumn.getColumnName()))) {
                continue;
            }
            columnDao.removeById(genColumn.getId());
        }
        List<GenColumn> result = columnDao.list(
                new LambdaQueryWrapper<GenColumn>()
                        .eq(GenColumn::getTableName, tableName)
        );
        return result;
    }

    @Override
    public List<GenColumn> listColumn(String schema, String tableName) {
        List<GenColumn> list = columnDao.list(
                new LambdaQueryWrapper<GenColumn>()
                        .eq(GenColumn::getTableName, tableName));
        return list;
    }
}
